package com.zxk.util;

import cn.hutool.core.io.FileUtil;
import com.sun.jna.platform.win32.User32;
import com.sun.jna.platform.win32.WinDef;
import com.zxk.constant.ParamConstants;
import com.zxk.constant.PicConstants;
import com.zxk.entity.FindPicResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.UUID;

/**
 * 像素找图算法工具类
 * 算法描述：
 * 屏幕截图，得到图A，(查找的目标图片为图B)；
 * 遍历图A的像素点，根据图B的尺寸，得到图B四个角映射到图A上的四个点；
 * 得到的四个点与图B的四个角像素点的值比较。如果四个点一样，执行步骤4；否则，回到步骤2继续；
 * 进一步对比，将映射范围内的全部点与图B全部的点比较。如果全部一样，则说明图片已找到；否则，回到步骤2继续；
 *
 * @author: 舟小亢
 * @date: 2023-08-06 23:08
 */
public class PixelPicUtil {

    private static Logger LOG = LoggerFactory.getLogger(PixelPicUtil.class);

    private static String PIC_BMP = ".bmp";

    /**
     * 默认循环找图并点击
     *
     * @param destImagePath
     * @return
     */
    public static boolean findAndClickRandom(WinDef.HWND hwnd_0, WinDef.HWND hwnd_1, String destImagePath) throws Exception {
        return findAndClickRandom(hwnd_0, hwnd_1, destImagePath, ParamConstants.FIND_MAX_TIMES);
    }


    /**
     * 找图并点击附带随机数的图片位置
     *
     * @param destImagePath
     * @return
     */
    public static boolean findAndClickRandom(WinDef.HWND hwnd_0, WinDef.HWND hwnd_1, String destImagePath, int maxTimes) throws Exception {
        // 找图需要用主程序的句柄，截图才能全
        FindPicResult pic = findPic(hwnd_0, destImagePath, maxTimes);
        if (!pic.isFinded()) {
            return false;
        }
        // 点击需要用子程序的句柄
        return MouseUtil.lClickRandom(hwnd_1, pic.getPicX(), pic.getPicY());
    }

    /**
     * 找图并点击附带随机数的图片位置
     *
     * @param destImagePath
     * @return
     */
    public static boolean findAndClickRandom(WinDef.HWND hwnd_0, WinDef.HWND hwnd_1, String destImagePath, double sim) throws Exception {
        // 找图需要用主程序的句柄，截图才能全
        FindPicResult pic = findPic(hwnd_0, destImagePath, sim);
        if (!pic.isFinded()) {
            return false;
        }
        // 点击需要用子程序的句柄
        return MouseUtil.lClickRandom(hwnd_1, pic.getPicX(), pic.getPicY());
    }

    /**
     * 找图并点击附带随机数的图片位置
     *
     * @param destImagePath
     * @return
     */
    public static boolean findAndClickRandom(WinDef.HWND hwnd_0, WinDef.HWND hwnd_1, String destImagePath, int maxTimes, double sim) throws Exception {
        // 找图需要用主程序的句柄，截图才能全
        FindPicResult pic = findPic(hwnd_0, destImagePath, maxTimes, sim);
        if (!pic.isFinded()) {
            return false;
        }
        // 点击需要用子程序的句柄
        return MouseUtil.lClickRandom(hwnd_1, pic.getPicX(), pic.getPicY());
    }

    public static FindPicResult findPic(WinDef.HWND hwnd, String destImagePath) throws Exception {
        return findPic(hwnd, destImagePath, ParamConstants.FIND_MAX_TIMES, ParamConstants.SIMILARITY);
    }

    public static FindPicResult findPic(WinDef.HWND hwnd, String destImagePath, int times) throws Exception {
        return findPic(hwnd, destImagePath, times, ParamConstants.SIMILARITY);
    }

    public static FindPicResult findPic(WinDef.HWND hwnd, String destImagePath, double sim) throws Exception {
        return findPic(hwnd, destImagePath, ParamConstants.FIND_MAX_TIMES, sim);
    }

    /**
     * 循环找单图
     *
     * @return
     */
    public static FindPicResult findPic(WinDef.HWND hwnd, String destImagePath, int times, double sim) throws Exception {
        times = times <= 0 ? ParamConstants.FIND_MAX_TIMES : times;
        String picName = destImagePath.substring(destImagePath.lastIndexOf(File.separator) + 1);
        FindPicResult pic = null;
        LOG.warn("开始找图[{}]!", picName);
        LOG.warn("目标路径[{}]!", destImagePath);
        try {
            for (int i = 0; i < times; i++) {
                String srcImagePath = getHwndPic(hwnd);
                try {
                    pic = findPic(srcImagePath, destImagePath);
                    if (pic.isFinded()) {
                        return pic;
                    }
                    LOG.warn("第[{}]次未找到!", i + 1);
                    ThreadUtil.sleep1();
                } finally {
                    // 删除临时图片
                    FileUtil.del(srcImagePath);
                }
            }
        } catch (Exception e) {
            LOG.error("对比图片错误", e);
            throw e;
        }

        return pic;
    }

    public static FindPicResult findPicM(WinDef.HWND hwnd, String[] destImagePaths) throws Exception {
        return findPicM(hwnd, destImagePaths, ParamConstants.FIND_MAX_TIMES);
    }

    /**
     * 循环找多图，找到返回1，没找到返回0
     *
     * @return
     */
    public static FindPicResult findPicM(WinDef.HWND hwnd, String[] destImagePaths, int times) throws Exception {
        times = times <= 0 ? ParamConstants.FIND_MAX_TIMES : times;
        String picName = "";
        for (String destImagePath : destImagePaths) {
            picName = picName + "|" + destImagePath.substring(destImagePath.lastIndexOf(File.separator) + 1);
        }
        int[] findResult = new int[destImagePaths.length];
        FindPicResult pic = new FindPicResult();
        pic.setFindResult(findResult);
        LOG.warn("开始找图[{}]!", picName);
        try {
            for (int i = 0; i < times; i++) {
                String srcImagePath = PixelPicUtil.getHwndPic(hwnd);
                try {
                    for (int j = 0; j < destImagePaths.length; j++) {
                        pic = findPic(srcImagePath, destImagePaths[j]);
                        if (pic.isFinded() == true) {
                            findResult[j] = 1;
                            pic.setFindResult(findResult);
                            return pic;
                        }
                    }
                    LOG.warn("第[{}]次未找到!", i + 1);
                    ThreadUtil.sleep2();
                } finally {
                    // 删除临时图片
                    FileUtil.del(srcImagePath);
                }
            }
        } catch (Exception e) {
            LOG.error("对比图片错误", e);
            throw e;
        }
        return pic;
    }

    /**
     * 获取指定句柄截图
     *
     * @param hwnd 句柄
     * @return 图片全路径
     */
    public static String getHwndPic(WinDef.HWND hwnd) {
        // 图片名字：随机数+后缀
        String picName = UUID.randomUUID() + PIC_BMP;
        String picAbsPath = PicConstants.TEMP_PIC_PATH + picName;
        // 获取屏幕尺寸
        WinDef.RECT rect = new WinDef.RECT();
        User32.INSTANCE.GetWindowRect(hwnd, rect);
        int width = rect.right - rect.left;
        int height = rect.bottom - rect.top;
        // 截图
        BufferedImage screenshot = ScreenCapUtil.getScreenshot(new Rectangle(0, 0, width, height), hwnd);
        // 写入文件
        try {
            ImageIO.write(screenshot, "bmp", new File(picAbsPath));
        } catch (IOException e) {
            LOG.error("截屏写入文件失败！", e);
        }
        return picAbsPath;
    }


    /**
     * 查找图片,100%像素点匹配算法
     *
     * @param srcImagePath  大图
     * @param destImagePath 小图
     * @return
     */
    public static FindPicResult findPic(String srcImagePath, String destImagePath) {
        BufferedImage srcImage = getBfImageFromPath(srcImagePath);
        BufferedImage destImage = getBfImageFromPath(destImagePath);
        int[][] srcImageRGBData = getImageGRB(srcImage);
        int[][] destImageRGBData = getImageGRB(destImage);
        int srcImgWidth = srcImage.getWidth();
        int srcImgHeight = srcImage.getHeight();
        int destImgWidth = destImage.getWidth();
        int destImgHeight = destImage.getHeight();

        int[][][] findImgData = new int[destImgHeight][destImgWidth][2];
        //遍历屏幕截图像素点数据
        for (int y = 0; y < srcImgHeight - destImgHeight; y++) {
            for (int x = 0; x < srcImgWidth - destImgWidth; x++) {
                //根据目标图的尺寸，得到目标图四个角映射到屏幕截图上的四个点，
                //判断截图上对应的四个点与图B的四个角像素点的值是否相同，
                //如果相同就将屏幕截图上映射范围内的所有的点与目标图的所有的点进行比较。
                if ((destImageRGBData[0][0] ^ srcImageRGBData[y][x]) == 0
                        && (destImageRGBData[0][destImgWidth - 1] ^ srcImageRGBData[y][x + destImgWidth - 1]) == 0
                        && (destImageRGBData[destImgHeight - 1][destImgWidth - 1] ^ srcImageRGBData[y + destImgHeight - 1][x + destImgWidth - 1]) == 0
                        && (destImageRGBData[destImgHeight - 1][0] ^ srcImageRGBData[y + destImgHeight - 1][x]) == 0) {
                    boolean isFinded = isMatchAll(y, x, srcImgHeight, srcImgWidth, destImgHeight, destImgWidth, srcImageRGBData, destImageRGBData);
                    //如果比较结果完全相同，则说明图片找到，填充查找到的位置坐标数据到查找结果数组。
                    if (isFinded) {
                        LOG.info("找到一处完全相同的[x:{} y:{}]", x, y);
                        for (int h = 0; h < destImgHeight; h++) {
                            for (int w = 0; w < destImgWidth; w++) {
                                findImgData[h][w][0] = x + w;
                                findImgData[h][w][1] = y + h;
                            }
                        }
                        FindPicResult findPicResult = new FindPicResult();
                        findPicResult.setPicX(findImgData[0][0][0]);
                        findPicResult.setPicY(findImgData[0][0][1]);
                        findPicResult.setFinded(true);
                        return findPicResult;
                    }
                }
            }
        }
        return new FindPicResult();
    }

    /**
     * 根据BufferedImage获取图片RGB数组
     *
     * @param bfImage
     * @return
     */
    public static int[][] getImageGRB(BufferedImage bfImage) {
        int width = bfImage.getWidth();
        int height = bfImage.getHeight();
        int[][] result = new int[height][width];
        for (int h = 0; h < height; h++) {
            for (int w = 0; w < width; w++) {
                //使用getRGB(w, h)获取该点的颜色值是ARGB，而在实际应用中使用的是RGB，所以需要将ARGB转化成RGB，即bufImg.getRGB(w, h) & 0xFFFFFF。
                result[h][w] = bfImage.getRGB(w, h) & 0xFFFFFF;
            }
        }
        return result;
    }

    /**
     * 从本地文件读取目标图片
     *
     * @param keyImagePath - 图片绝对路径
     * @return 本地图片的BufferedImage对象
     */
    public static BufferedImage getBfImageFromPath(String keyImagePath) {
        BufferedImage bfImage = null;
        try {
            bfImage = ImageIO.read(new File(keyImagePath));
        } catch (IOException e) {
            e.printStackTrace();
        }
        return bfImage;
    }


    /**
     * 判断屏幕截图上目标图映射范围内的全部点是否全部和小图的点一一对应。
     *
     * @param y                - 与目标图左上角像素点想匹配的屏幕截图y坐标
     * @param x                - 与目标图左上角像素点想匹配的屏幕截图x坐标
     * @param srcImgHeight
     * @param srcImgWidth
     * @param destImgHeight
     * @param destImgWidth
     * @param srcImageRGBData
     * @param destImageRGBData
     * @return
     */
    public static boolean isMatchAll(int y, int x, int srcImgHeight, int srcImgWidth, int destImgHeight, int destImgWidth, int[][] srcImageRGBData, int[][] destImageRGBData) {
        int biggerY = 0;
        int biggerX = 0;
        int xor = 0;
        for (int smallerY = 0; smallerY < destImgHeight; smallerY++) {
            biggerY = y + smallerY;
            for (int smallerX = 0; smallerX < destImgWidth; smallerX++) {
                biggerX = x + smallerX;
                if (biggerY >= srcImgHeight || biggerX >= srcImgWidth) {
                    return false;
                }
                xor = destImageRGBData[smallerY][smallerX] ^ srcImageRGBData[biggerY][biggerX];
                if (xor != 0) {
                    return false;
                }
            }
            biggerX = x;
        }
        return true;
    }

}
